package top.goldenyear.porpoise.framework.web.core.generator;

import cn.hutool.v7.core.io.file.FileUtil;
import cn.hutool.v7.core.util.CharsetUtil;
import com.jfinal.kit.JavaKeyword;
import com.jfinal.kit.Kv;
import com.jfinal.template.Engine;
import com.jfinal.template.expr.ast.FieldGetters;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import top.goldenyear.porpoise.common.core.util.string.StrUtils;
import top.goldenyear.porpoise.framework.view.core.enjoy.FileAndClassPathSourceFactory;
import top.goldenyear.porpoise.framework.web.core.generator.config.DomainConfig;
import top.goldenyear.porpoise.framework.web.core.generator.config.GlobalConfig;
import top.goldenyear.porpoise.framework.web.core.generator.config.ModuleConfig;
import top.goldenyear.porpoise.framework.web.core.generator.model.ClassMeta;
import top.goldenyear.porpoise.framework.web.core.generator.model.TableMeta;

/**
 * 代码生成器
 *
 * @author airhead
 */
@Getter
@Setter
@Slf4j
public class CodeEngine {
  protected ModuleConfig moduleConfig;
  protected GlobalConfig globalConfig;
  protected Engine engine;
  protected Map<String, String> getterTypeMap =
      new HashMap<>() {
        {
          put("java.lang.String", "getStr");
          put("java.lang.Integer", "getInt");
          put("java.lang.Long", "getLong");
          put("java.lang.Double", "getDouble");
          put("java.lang.Float", "getFloat");
          put("java.lang.Short", "getShort");
          put("java.lang.Byte", "getByte");

          // 新增两种可自动转换类型的 getter 方法
          put("java.util.Date", "getDate");
          put("java.time.LocalDateTime", "getLocalDateTime");

          // 新增 TypeKit 转换类之后，支持了更多的类型
          put("java.lang.Boolean", "getBoolean");
          put("java.math.BigDecimal", "getBigDecimal");
          put("java.math.BigInteger", "getBigInteger");
        }
      };
  protected JavaKeyword javaKeyword = JavaKeyword.me;

  public CodeEngine() {
    this.engine =
        Engine.createIfAbsent(
            "CodeGenerator",
            engine -> {
              engine.addSharedStaticMethod(StrUtils.class);
              engine.setSourceFactory(new FileAndClassPathSourceFactory());

              // 以下配置将支持 user.girl 表达式去调用 user 对象的 boolean isGirl() 方法
              Engine.addFieldGetterToFirst(new FieldGetters.IsMethodFieldGetter());
              engine.setStaticFieldExpression(true);
              engine.setStaticMethodExpression(true);
              engine.addSharedObject("getterTypeMap", this.getterTypeMap);
              engine.addSharedObject("javaKeyword", this.javaKeyword);
            });
  }

  public Kv buildBindingData(TableMeta tableMeta) {
    ClassMeta classMeta = ClassMeta.covert(tableMeta, globalConfig, moduleConfig);
    return buildBindingData(classMeta);
  }

  public Kv buildBindingData(ClassMeta classMeta) {
    Kv bindingData = Kv.create();
    bindingData.set("cls", classMeta);
    bindingData.set("fds", classMeta.getFields());

    bindingData.set("moduleConfig", moduleConfig);
    bindingData.set("moduleConfigs", globalConfig.getModuleConfigs());
    DomainConfig domainConfig =
        globalConfig.getStrategyConfig().getDomainConfig(classMeta.getTable());
    bindingData.set("tables", domainConfig.getTables());

    bindingData.set("author", globalConfig.getAuthor());
    bindingData.set("since", globalConfig.getSince());

    bindingData.set("resourcesRoot", globalConfig.getPackageConfig().getResourcesRoot());
    bindingData.set("sourcesRoot", globalConfig.getPackageConfig().getSourcesRoot());
    bindingData.set("testSourcesRoot", globalConfig.getPackageConfig().getTestSourcesRoot());
    bindingData.set("testResourcesRoot", globalConfig.getPackageConfig().getTestSourcesRoot());
    bindingData.set("basePackage", classMeta.getBasePackage());
    bindingData.set("parentPackage", classMeta.getParentPackage());
    bindingData.set("modulePackage", classMeta.getModulePackage());
    bindingData.set("packagePath", classMeta.getPackagePath());
    bindingData.set("moduleFile", classMeta.getModuleFile());
    bindingData.set("chainSetter", moduleConfig.getChainSetter());

    bindingData.set("uiSourcesRoot", globalConfig.getUiConfig().getSourceRoot());
    bindingData.set("moduleKebab", classMeta.getModuleKebab());
    bindingData.set("moduleKebabFile", classMeta.getModuleKebabFile());

    // TODO: 处理树表的信息

    // TODO: 处理主子表的信息

    return bindingData;
  }

  public Kv generateCode(Kv bindingData) {
    String outputFile = getOutputFile(bindingData);
    String outputDir = FileUtil.getParent(outputFile, 1);

    Kv kv =
        Kv.create()
            .set("file", outputFile)
            .set("dir", outputDir)
            .set("overwrite", moduleConfig.needWrite());

    String template = getTemplate(bindingData);
    String content = engine.getTemplate(template).renderToString(bindingData);
    kv.set("content", content);

    return kv;
  }

  public void generateFile(Kv kvContent) {
    String outDir = kvContent.getStr("dir");
    String outFile = kvContent.getStr("file");

    log.info(
        "Generate Code, module: {}, dir: {}, file: {}", moduleConfig.getModule(), outDir, outFile);

    File dir = new File(outDir);
    if (!FileUtil.exists(dir)) {
      FileUtil.mkdir(dir);
    }

    File file = new File(outFile);
    if (FileUtil.exists(file)) {
      if (Boolean.FALSE.equals(kvContent.getBoolean("overwrite"))) {
        return;
      }
    }

    FileUtil.writeString(kvContent.getStr("content"), file, CharsetUtil.UTF_8);
  }

  protected String getOutputFile(Kv bindingData) {
    return engine.getTemplateByString(moduleConfig.getOutputFile()).renderToString(bindingData);
  }

  protected String getTemplate(Kv bindingData) {
    return engine.getTemplateByString(moduleConfig.getTemplate()).renderToString(bindingData);
  }

  protected String prettyCode(String content) {
    // TODO: 代码格式化
    return content;
  }
}
